
import os
import json
import time
import asyncio
import datetime
import threading
import xlwings as xw
import pyppeteer as pyp
from tkinter import *
from tkinter.ttk import *
from tkinter.filedialog import askopenfilename

import logging

logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s | %(levelname)s | %(module)s:%(funcName)s:%(lineno)d - %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
    handlers=[
        logging.FileHandler(filename="./info.log"),
        # logging.StreamHandler(level=logging.DEBUG),
    ],
)

logger = logging.getLogger(__name__)


class WinGUI(Tk):

    def __init__(self):
        super().__init__()
        self.__win()

        self.browser_path = StringVar()
        self.script1_path = StringVar()
        self.script2_path = StringVar()
        self.script_radio = IntVar()
        self.script_radio.set(1)

        self.my_conf = None

        # 表格
        self.tk_table_lvyxskio = self.__tk_table_lvyxskio(self)
        self.tk_label_frame_lvyxt3x4 = self.__tk_label_frame_lvyxt3x4(self)
        self.tk_label_lvyxuuni = self.__tk_label_lvyxuuni( self.tk_label_frame_lvyxt3x4)

        self.tk_input_lvyxuy1d = self.__tk_input_lvyxuy1d( self.tk_label_frame_lvyxt3x4) 
        self.tk_label_lvyxvxua = self.__tk_label_lvyxvxua( self.tk_label_frame_lvyxt3x4) 
        self.tk_input_lvyxw8rq = self.__tk_input_lvyxw8rq( self.tk_label_frame_lvyxt3x4) 
        self.tk_input_lvyxxwjz = self.__tk_input_lvyxxwjz( self.tk_label_frame_lvyxt3x4)
        #
        self.tk_label_lvyxxz4o = self.__tk_label_lvyxxz4o( self.tk_label_frame_lvyxt3x4)
        # 开始
        self.tk_button_lvyxzk4x = self.__tk_button_lvyxzk4x( self.tk_label_frame_lvyxt3x4)
        # 退出
        self.tk_button_lvyxzlvl = self.__tk_button_lvyxzlvl( self.tk_label_frame_lvyxt3x4) 
        self.tk_radio_button_lvyy070j = self.__tk_radio_button_lvyy070j( self.tk_label_frame_lvyxt3x4) 
        self.tk_radio_button_lvyy08rz = self.__tk_radio_button_lvyy08rz( self.tk_label_frame_lvyxt3x4)
        self.tk_text_lvyxti91 = self.__tk_text_lvyxti91(self)

        filename = "./conf.json"
        if os.path.exists(filename):
            self.browser_path.set(filename)
            conf = self.read_conf(filename)
            self.set_settings(conf)
        else:
            print("文件不存在")

    def __win(self):
        self.title("AUTO SAP VBA")
        # 设置窗口大小、居中
        width = 610
        height = 330
        screenwidth = self.winfo_screenwidth()
        screenheight = self.winfo_screenheight()
        geometry = '%dx%d+%d+%d' % (width, height, (screenwidth - width) / 2, (screenheight - height) / 2)
        self.geometry(geometry)
        self.resizable(width=False, height=False)

    def scrollbar_autohide(self,vbar, hbar, widget):
        """自动隐藏滚动条"""
        def show():
            if vbar: vbar.lift(widget)
            if hbar: hbar.lift(widget)
        def hide():
            if vbar: vbar.lower(widget)
            if hbar: hbar.lower(widget)
        hide()
        widget.bind("<Enter>", lambda e: show())
        if vbar: vbar.bind("<Enter>", lambda e: show())
        if vbar: vbar.bind("<Leave>", lambda e: hide())
        if hbar: hbar.bind("<Enter>", lambda e: show())
        if hbar: hbar.bind("<Leave>", lambda e: hide())
        widget.bind("<Leave>", lambda e: hide())

    def v_scrollbar(self,vbar, widget, x, y, w, h, pw, ph):
        widget.configure(yscrollcommand=vbar.set)
        vbar.config(command=widget.yview)
        vbar.place(relx=(w + x) / pw, rely=y / ph, relheight=h / ph, anchor='ne')

    def h_scrollbar(self,hbar, widget, x, y, w, h, pw, ph):
        widget.configure(xscrollcommand=hbar.set)
        hbar.config(command=widget.xview)
        hbar.place(relx=x / pw, rely=(y + h) / ph, relwidth=w / pw, anchor='sw')

    def create_bar(self,master, widget,is_vbar,is_hbar, x, y, w, h, pw, ph):
        vbar, hbar = None, None
        if is_vbar:
            vbar = Scrollbar(master)
            self.v_scrollbar(vbar, widget, x, y, w, h, pw, ph)
        if is_hbar:
            hbar = Scrollbar(master, orient="horizontal")
            self.h_scrollbar(hbar, widget, x, y, w, h, pw, ph)
        self.scrollbar_autohide(vbar, hbar, widget)

    def __tk_table_lvyxskio(self, parent):
        # 表头字段 表头宽度
        columns = {"序号": 40, "脚本名称": 80, "执行内容": 175}
        tk_table = Treeview(parent, show="headings", columns=list(columns),)
        for text, width in columns.items():  # 批量设置列属性
            tk_table.heading(text, text=text, anchor='center')
            tk_table.column(text, anchor='center', width=width, stretch=False)  # stretch 不自动拉伸
        tk_table.place(x=305, y=15, width=300, height=205)
        return tk_table

    def empty_tab(self):
        self.tk_table_lvyxskio.delete(*self.tk_table_lvyxskio.get_children())

    def tab_edit(self):
        # 获取要编辑的选择项
        selected_item = self.tk_table_lvyxskio.selection()[0]
        self.tk_table_lvyxskio.item(selected_item, text="blub", values=("foo", "bar"))

    def tab_delete(self):
        # 获取要删除的选择项
        selected_item = self.tk_table_lvyxskio.selection()[0]
        self.tk_table_lvyxskio.delete(selected_item)

    def __tk_label_frame_lvyxt3x4(self,parent):
        frame = LabelFrame(parent, text="配置信息",)
        frame.place(x=5, y=5, width=300, height=215)
        return frame

    def __tk_label_lvyxuuni(self,parent):
        label = Label(parent, text="浏览器：", anchor="center")
        label.bind("<Button-1>", self.set_conf)
        label.place(x=10, y=0, width=50, height=30)
        return label

    def set_conf(self, event=None):
        self.get_file(event)
        path = self.browser_path.get()
        conf = self.read_conf(path)
        self.set_settings(conf)

    def set_settings(self, conf):

        print(conf)
        browser_path = conf.get("browser_path")
        run_script = conf.get("run_script")
        open_and_exe = conf.get("open_and_exe")
        script1 = conf.get("script1")
        script1_path_ = script1.get("path")
        script1_steps = script1.get("step")

        self.empty_tab()
        for i, step in enumerate(script1_steps):
            self.tk_table_lvyxskio.insert("", "end", text=f"{i}",
                             values=(f"{i}", "script1", step))
        script2 = conf.get("script2")
        script2_path_ = script2.get("path")
        script2_steps = script2.get("step")
        for i, step in enumerate(script2_steps):
            i += len(script1_steps)
            self.tk_table_lvyxskio.insert("", "end", text=f"{i}",
                             values=(f"{i}", "script2", step))
        self.script1_path.set(script1_path_)
        self.script2_path.set(script2_path_)
        self.script_radio.set(run_script)

        self.my_conf = conf
        if open_and_exe == 1:
            self.log("开启即执行...")
            self.start_script()

    def get_file(self, event):
        d = askopenfilename()
        if d:
            self.browser_path.set(d)

    def __tk_input_lvyxuy1d(self, parent):
        ipt = Entry(parent, textvariable=self.browser_path)
        ipt.place(x=70, y=0, width=210, height=30)
        return ipt

    def __tk_label_lvyxvxua(self,parent):
        label = Label(parent, text="脚本1：", anchor="center", )
        label.place(x=10, y=40, width=50, height=30)
        return label

    def __tk_input_lvyxw8rq(self,parent):
        ipt = Entry(parent, textvariable=self.script1_path)
        ipt.place(x=70, y=40, width=210, height=30)
        return ipt

    def __tk_input_lvyxxwjz(self,parent):
        ipt = Entry(parent, textvariable=self.script2_path)
        ipt.place(x=70, y=80, width=210, height=30)
        return ipt

    def __tk_label_lvyxxz4o(self,parent):
        label = Label(parent, text="脚本2：", anchor="center", )
        label.place(x=10, y=80, width=50, height=30)
        return label

    def __tk_button_lvyxzk4x(self,parent):
        btn = Button(parent, text="开始", takefocus=False, command=self.start_script)
        btn.place(x=70, y=160, width=50, height=30)
        return btn

    def start_script(self):
        threading.Thread(target=self.__start_script).start()


    def __start_script(self):
        selected_item = self.tk_table_lvyxskio.selection()
        # 未选中时 默认取第一个
        selected_item = "I001" if len(selected_item) == 0 else selected_item[0]
        print(selected_item)
        item_text = self.tk_table_lvyxskio.item(selected_item, "values")
        print(item_text)
        """
        x = self.tk_table_lvyxskio.get_children()
        for item in x:
            print(type(item))
            print(item)
        """
        browser_path = self.my_conf.get("browser_path")
        script1_path = self.script1_path.get()
        script2_path = self.script2_path.get()
        script_radio = self.script_radio.get()
        msg = f"path1:{browser_path} path2:{script1_path}, path3:{script2_path} radio:{script_radio}"
        self.log(msg)

        param = {
            "userDataDir": r"./temp_chrome",
            "browser_path": self.my_conf.get("browser_path"),
            "username": self.my_conf.get("username"),
            "password": self.my_conf.get("password"),
            "log_func": self.log
        }
        self.log(" 开始执行SAP登录...")
        sap = LoginSAP(param)
        sap.run()
        if not sap.login_:
            self.log(" SAP login error!")
            return
        self.log(" SAP login succeed")

        self.log(" 等待SAP启动...")
        time.sleep(5)
        self.log(" 开始执行VBA...")

        # 脚本类别
        if script_radio == 2:
            vba_script = self.my_conf.get("script2")
        else:
            vba_script = self.my_conf.get("script1")

        script_path = vba_script.get("path")
        script_steps = vba_script.get("step")
        param = {
            "path": script_path,
            "funcs": script_steps,
            "log_func": self.log
        }
        vba = ExecuteVBA(**param)
        rest = vba.run()
        if not vba.exe_success:
            self.log(" VBA execute error!")
            return
        self.log(" VBA execute succeed")
        self.log("执行结束.....")

    def __tk_button_lvyxzlvl(self, parent):
        btn = Button(parent, text="退出", takefocus=False, command=self.quit)
        btn.place(x=180, y=160, width=50, height=30)
        return btn

    def __tk_radio_button_lvyy070j(self, parent):
        rb = Radiobutton(parent, text="脚本1", value=1,  variable=self.script_radio)
        rb.place(x=70, y=120, width=80, height=30)
        return rb

    def __tk_radio_button_lvyy08rz(self, parent):
        rb = Radiobutton(parent, text="脚本2", value=2, variable=self.script_radio)
        rb.place(x=170, y=120, width=80, height=30)
        return rb

    def __tk_text_lvyxti91(self,parent):
        #text = Text(parent, bg='black', fg='white')
        text = Text(parent, bg='black', fg='green')
        #text = Text(parent)
        text.place(x=5, y=222, width=600, height=100)
        return text

    @staticmethod
    def __log(widget, string):

        # logging.debug(f"This is a debug message")
        logging.info(string)
        # logging.warning("This is a warning message")
        # logging.error("This is an error message")

        # 将新日志添加到Text组件
        widget.config(state='normal')  # 设置组件为可编辑状态
        widget.insert('end', f"{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')} {string}\n")  # 插入最新10条日志
        widget.yview('end')  # 滚动到Text组件底部
        widget.config(state='disabled')  # 设置组件为不可编辑状态，防止用户修改

    def log(self, string):
        update_thread = threading.Thread(target=self.__log, args=(self.tk_text_lvyxti91, string))
        update_thread.start()

    @staticmethod
    def read_conf(path) -> dict:
        with open(path) as f:
            dt = f.readlines()
        return json.loads("".join(dt))


class LoginSAP:

    def __init__(self, param:dict):
        self.userDataDir = param.get("userDataDir", r"./temp_chrome")
        self.executablePath = param.get("executablePath", r'C:\Users\30054862\AppData\Local\pyppeteer\pyppeteer\local-chromium\588429\chrome-win32\chrome.exe')

        self.username = param.get("username", "30040501")
        self.password = param.get("password", "Y0kogawa#")
        self.log = param.get("log_func")
        self.login_ = False

    def run(self):

        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        thread = threading.Thread(target=loop.run_until_complete, args=(self.login_sap(),))
        thread.start()
        thread.join()
        #asyncio.get_event_loop().run_until_complete(self.login_sap())

    async def login_sap(self):
        width, height = 1400, 600  # 网页宽高
        browser = await pyp.launch(handleSIGINT=False,
                                   # 这三个见https://blog.csdn.net/weixin_41822224/article/details/103719863
                                   handleSIGTERM=False,  # 用于 多线程pyppeteer 遇到的一个问题
                                   handleSIGHUP=False,  # ValueError: signal only works in main threadpyppeteer
                                   headless=False,
                                   userDataDir=self.userDataDir,
                                   args=[f'--window-size={width},{height + 500}', '--disable-infobars'],
                                   executablePath=self.executablePath)
        self.log("启动浏览器")
        try:
            await self.__login_sap(browser)
        except Exception as e:
            self.log(e.args)
        finally:
            await browser.close()

    async def __login_sap(self, browser):

        page = await browser.newPage()

        self.log("进入主页")
        # 主页登录
        url = "http://30040501:Y0kogawa%23@10.48.162.80"
        await page.goto(url)

        self.log("寻找SAP入口")
        sap_place_xpath = '//*[@id="WebPartctl00_ctl40_g_1cd0cb1d_81cc_4768_a517_50f1b31eb940"]/table/tbody/tr[2]/td/div[9]'
        sap_place_selector = '#WebPartctl00_ctl40_g_1cd0cb1d_81cc_4768_a517_50f1b31eb940 > table > tbody > tr:nth-child(2) > td > div:nth-child(9)'
        sap_place_txt = await page.querySelector(sap_place_selector)
        sap_place_txt_ = await page.evaluate('(sap_place_txt) => sap_place_txt.textContent', sap_place_txt)
        if sap_place_txt_ != "SAP":
            msg = "未找到SAP入口"
            self.log(msg)
            raise RuntimeError(msg)

        self.log("进入SAP")
        # SAP 入口地址
        sap_place_xpath_a = '//*[@id="WebPartctl00_ctl40_g_1cd0cb1d_81cc_4768_a517_50f1b31eb940"]/table/tbody/tr[2]/td/div[9]/a'
        sap_place_xpath_a_value = await page.xpath(sap_place_xpath_a)
        sap_url = await (await sap_place_xpath_a_value[0].getProperty("href")).jsonValue()

        # 跳转至sap
        is_in = False
        for _ in range(10):
            try:
                # 一般重试3次进入登录界面
                await asyncio.wait_for(page.goto(sap_url), timeout=3)
                is_in = True
                break
            except asyncio.TimeoutError:
                msg = f"try {_}"
                print(msg)
                self.log(msg)

        if is_in:
            self.log("进入SAP成功")
            await asyncio.sleep(1)
            try:

                try:
                    self.log("尝试二次登录")
                    # html 二次登录 密码输入, 可能会存在直接成功跳转的情况
                    username_xpath = '//*[@id="username"]'
                    password_xpath = '//*[@id="password"]'
                    username_pathx_txt = await page.xpath(username_xpath)
                    await username_pathx_txt[0].type(self.username, delay=3)
                    password_xpath_txt = await page.xpath(password_xpath)
                    await password_xpath_txt[0].type(self.password, delay=3)
                    login_xpath = "/html/body/div/div[2]/div/form/div[6]/a"
                    login_xpath_a = await page.xpath(login_xpath)
                    await login_xpath_a[0].click()
                    await asyncio.sleep(1)
                except Exception as e:
                    msg = "未找到二次登录界面"
                    print(msg)
                    self.log(msg)

                self.log("尝试点击下载SAP启动文件")
                await asyncio.sleep(2)
                new_url = 'http://zdb001-0m9113.ygsp.ykgw.net:50000/irj/servlet/prt/portal/prtroot/pcd!3aportal_content!2fcom.yokogawa.yokogawa!2fcm!2fiview!2fIVGCM_SAP_ECC_ACCOUNTING_AND_LOGISTICS?sap-config-mode=true'
                page_sap = await browser.newPage()
                page_sap_cdp = await page_sap.target.createCDPSession()
                # 这里不能写./(会下载到浏览器驱动的目录)
                downloadPath = r'D:\ipythonworkspace\ManaAPI\sap_script'
                await page_sap_cdp.send('Page.setDownloadBehavior', {'behavior': 'allow',
                                                                     'downloadPath': downloadPath})  # 设置下载路径
                # 跳转至下载
                await page_sap.goto(new_url)
                await asyncio.sleep(2)
                time.sleep(3)

                filename = os.path.join(downloadPath, "tx.sap")
                if not os.path.exists(filename):
                    msg = "SAP登录文件下载失败!"
                    self.log(msg)
                    raise RuntimeError(msg)

                msg = "SAP登录文件下载成功"
                self.log(msg)
                rest = os.system(filename)
                if rest != 0:
                    msg = "执行SAP登录脚本异常！"
                    self.log(msg)
                    raise RuntimeError("执行SAP登录脚本异常！")
                self.login_ = True

            except Exception as e:
                raise RuntimeError("验证失败！")
        else:
            raise RuntimeError("进入失败！")


class ExecuteVBA:

    def __init__(self, path: str = None, funcs: list = None, log_func=None):

        self.path = r"C:\Users\30054862\Desktop\test.xlsm" if path is None else path
        self.funcs = ['python_test'] if funcs is None else funcs
        self.log = log_func
        self.exe_success = False

    def run(self):
        # visible表示程序运行时是否可见Excel,True表示可见，False表示不可见；add_book表示是否要新建工作簿
        app = xw.App(visible=False, add_book=False)
        #app = xw.App(visible=True, add_book=False)
        path = self.path
        if not os.path.exists(path):
            raise RuntimeError("执行失败！")

        res = {}
        try:
            wb = app.books.open(path)
            for func_name in self.funcs:
                func = wb.macro(func_name)
                rest = func()
                res[func_name] = rest
                self.log(f"函数=>{func_name} exe success:{rest}")
            self.exe_success = True
            wb.save(path)
            wb.close()
        except Exception as e:
            pass
        finally:
            app.kill()
        return res


if __name__ == "__main__":
    win = WinGUI()
    win.mainloop()